package aaa




// import aaa.utils.HashUtil
// import jakarta.persistence.*
import com.fasterxml.jackson.annotation.JsonFormat
import com.fasterxml.jackson.annotation.JsonIgnore
import io.swagger.v3.oas.annotations.Hidden
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.persistence.AttributeOverride
import jakarta.persistence.CascadeType
import jakarta.persistence.CollectionTable
import jakarta.persistence.Column
import jakarta.persistence.ElementCollection
import jakarta.persistence.Entity
import jakarta.persistence.EntityManagerFactory
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.MappedSuperclass
import jakarta.persistence.OneToOne
import jakarta.persistence.OrderColumn
import jakarta.persistence.Table
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
import jakarta.persistence.metamodel.SingularAttribute
import jakarta.persistence.metamodel.StaticMetamodel
import java.lang.Math
import java.security.NoSuchAlgorithmException
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.ArrayList
import java.util.Arrays
import java.util.Calendar
import java.util.Date
// import java.util.HashMap
// import java.util.List
// import java.util.Map
import java.util.Objects
import java.util.Random
import kotlin.concurrent.Volatile
import org.springframework.data.jpa.domain.Specification
import aaa.generated_pb.ProcessInfo
import aaa.generated_pb.BashHistoryEntry
import jakarta.persistence.criteria.Path

import aaa.generated_pb.ConnectionStat
import aaa.generated_pb.GetNetstatsInfoRpl
import aaa.generated_pb.InboundRule
import aaa.generated_pb.InboundRuleType
import aaa.generated_pb.LoginInfo
import aaa.generated_pb.UserInfo
import aaa.generated_pb.GetResourceInfoRpl
import aaa.generated_pb.OneoffProcessInfo
import com.fasterxml.jackson.annotation.JsonProperty

/*
 var code: Int
 @JvmField
 var data: T
 var msg: String

 constructor(code: Int, data: T, msg: String = "success") {
 this.code = code
 this.data = data
 this.msg = msg
 }
 */
class W<T>(var code: Int, var data: T, var msg: String = "success")



object Dt {



    interface ITeXWorkable {
        fun toTeXLine(): String

        fun getTeXEmpty(): String

        fun getTeXTemplate(): String
    }

    // java 9
    fun old2New(d: Date?): LocalDateTime = LocalDateTime.ofInstant(d!!.toInstant(), ZoneId.systemDefault())

    fun new2Old(l: LocalDateTime): Date = Date.from(l.atZone(ZoneId.systemDefault()).toInstant())

    val dtf: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

    val cmds: Array<String> =
        arrayOf(
            "ls -l", "ps -ef", "cat /etc/passwd",
            "cat /etc/shadow", "cat /etc/group", "cat /etc/gshadow",
            "cat /etc/hosts", "cat /etc/hostname", "cat /etc/resolv.conf",
            "cat /etc/ssh/sshd_config", "cat /etc/ssh/ssh_config",
            "ps -ef | grep ssh", "ps -ef | grep telnet", "ps -ef | grep ftp",
        )

    val users: Array<String> = arrayOf("root", "me", "aaa", "postgres", "mysql", "redis", "mongodb", "bbb", "admin")

    val apps: Array<String> =
        arrayOf(
            "mysql", "redis", "mongodb", "postgres", "nginx", "apache", "tomcat", "java", "python", "nodejs", "docker",
            "k8s", "etcd", "consul", "vault", "haproxy", "keepalived", "nginx", "apache", "tomcat", "java", "python",
            "nodejs", "cfw", "mariadb",
        )

    class Page<T>(var voList: List<T> , var totalCount: Long)

    // --------------------------------------------------
    // data

    class OldLoginHistory(
        var userName: String = "",
        var loginTime: Date? = null,
        var logoutTime: String = "", // special values: crash, timeout
        var loginTerminal: String = "",
        var loginFrom: String = "",
        var reason: Int = 0, // 1: 未授权登录， 2： 超出有效时间
    ){
        val recordHash: Int
            get() = hashCode()  //the hash of the record (🦜 : method from Object)

        companion object {
            fun oldFromNew(i: LoginInfo2): OldLoginHistory =
                OldLoginHistory(
                    userName = i.user,
                    loginTime = i.loginTime,
                    logoutTime = i.logoutTime,
                    loginTerminal = i.terminal,
                    loginFrom = i.loginIp,
                    reason = 0, //🦜 : Currently we put no restriction on this one.
                )
        }
    }                           // class OldLoginHistory

    class OldPresoftStatus(
        var code: Int = 0,
        var pid: String = "1234",
        var preSoftHash: String? = null,
        var interactCount: Long = 0,
        var LastCheck: Date = Date(),
    ){

        init {
            try {
                this.preSoftHash = Utils.generateRandomSha256Hash()
            } catch (e: NoSuchAlgorithmException) {
                this.preSoftHash = "1234567890123456789012345678901234567890123456789012345678901234"
            }
            this.interactCount = 100L
            val cal = Calendar.getInstance()
            cal.add(Calendar.DATE, -1)
            this.LastCheck = cal.time
        }

        companion object {
            var o: OldPresoftStatus = OldPresoftStatus()
            fun next(): OldPresoftStatus {
                val rand = Random()
                o.interactCount += rand.nextInt(10).toLong()
                return o
            }
        }

    }                           //OldPresoftStatus


    class OldBashHistoryDao(
        var userName: String = "",
        var cmd: String = "",
        var optTime: String = "",
        // var isIllegal: Boolean = false,
    ){

        val recordHash: Int
            get() = hashCode()  //the hash of the record (🦜 : method from Object)

        fun getIsIllegal(): Boolean = false

        /* 🦜 : methods starting with `is` is special to Jackson.. So.. yeah, we
           need to write it this way so that in json it's `isIllegal` but not
           `illegal` */

        companion object {
            fun oldFromNew(i: BashHistoryEntry2): OldBashHistoryDao =
                OldBashHistoryDao(
                    userName = i.user,
                    cmd = i.cmd,
                    optTime = i.time,
                )
        }
    }


    class OldTrustablePointsVo(
        var totalPoints: Double = 0.0,
        var envPoints: Double = 0.0,
        var actionPoints: Double = 0.0,
        var userPoints: Double = 0.0,
        var contentPoints: Double = 0.0,
    ){
        companion object {
            fun mock(): OldTrustablePointsVo {
                val o = OldTrustablePointsVo()
                o.envPoints = 80.2
                o.actionPoints = 99.3
                o.userPoints = 100.0
                o.contentPoints = 100.0
                // average
                o.totalPoints = (o.envPoints + o.actionPoints + o.userPoints + o.contentPoints) / 4
                return o
            }
        }
    }                           //OldTrustablePointsVo

    class OldRunDetail(
        var boot_time: Date? = null,
    ){
        val hourSinceBoot: Int
            get() {
                val d = old2New(boot_time)
                val now = LocalDateTime.now()
                val duration = Duration.between(d, now)
                return duration.toHours().toInt()
            }
    }                           //OldRunDetail

    /*

    @Data
    class OldMachineUserInfo {
        var id: String? = null
        var name: String? = null
        var workDir: String? = null
        var binShell: String? = null
        var lastLogin: Date? = null
        var legal: Boolean? = null

    }
     */

    class OldMachineUserInfo(
        var id: String? = null,
        var name: String? = null,
        var workDir: String? = null,
        var binShell: String? = null,
        var lastLogin: Date? = null,
        var legal: Boolean = true,
    ){

        companion object {
            fun oldFromNew(i: UserInfo2): OldMachineUserInfo {
                val o =
                    OldMachineUserInfo(
                    id = i.uid,
                    name = i.name,
                    workDir = i.homeDir,
                    binShell = i.shell,
                )

                val cal = Calendar.getInstance()
                cal.add(Calendar.DATE, -1)
                o.lastLogin = cal.time

                return o
            }
        }
    }                           //OldMachineUserInfo



    /*
     message OneoffProcessInfo {
     int64 unixMilli = 1;          // ms since epoch (🦜: maybe (-)ive)
     string comm = 2;
     uint32 pid = 3;
     uint32 ppid = 4;
     uint32 uid = 5;
     string argv = 6;
     int32 retVal = 7;
     }
     */


    @Entity
    class OneoffProcessInfo2(
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0,
        var time: Date? = null,
        var comm: String = "",
        var pid: Int = -1,
        var ppid: Int = -1,
        var uid: Int = -1,
        var argv: String = "",  // this is capped to 128 at the agent side. (the C code)
        var retVal: Int = 0,
    ){
        companion object {
            fun convertFromPb(i: OneoffProcessInfo): OneoffProcessInfo2 {
                var o =  OneoffProcessInfo2(
                    time = Date(i.unixMilli),
                    //🦜 : the `long` ctor for `Date` accepts ms
                    comm = i.comm,
                    pid = i.pid,
                    ppid = i.ppid,
                    uid = i.uid,
                    argv = i.argv,
                    retVal = i.retVal,
                )

                return o

            }
        }
    }

    /* [2024-02-29] 🦜 : Update: this is for a process that is captured through
       `ps`, so we don't need to persist it in db. Later, we will create a new
       class called `OneoffProcessInfo2`, these are the processes that are
       captured through snooping the `execve` syscall, and those probably should be persisted instead
     */

    class ProcessInfo2(
        var name: String = "",
        var user: String = "",
        var pid: Int = -1,
        var ppid: Int = -1,
        var cwd: String = "",
        var cpuPercent: Double = 0.0,
        var memPercent: Float = 0.0f,
        var readCount: Long = 0,
        var writeCount: Long = 0,
        var readBytes: Long = 0,
        var writeBytes: Long = 0,
        var createTime: String = "",
        var isRunning: Boolean = false,
        var isForeground: Boolean = false,
        var cmdline: MutableList<String> = ArrayList(),
        var exePath: String = "",
        var hash: String = "",
    ) {
        abstract class TeXBar(
            var xe: Double = 0.0,
            var text: String = "",
            var n: Int = 0,
            var color: String = "",
        ) : ITeXWorkable {

            override fun getTeXEmpty(): String = ""

            override fun toTeXLine(): String {
                // \aSimpleBar{\x}{-2\y}{7cm}{\texttt{node[pid=1234] 45\%}}
                val xee = String.format("%.2fcm", 0.2 + xe)
                val yy = String.format("%.2fcm", -n!! * y)
                val r = "\\aSimpleBar[$color]{0.2cm}{$yy}{$xee}{$text}"
                return r.replace("_".toRegex(), "\\\\_")
            }

            companion object {
                var y: Double = 1.0
                val colorGradients: List<Int> = listOf(90, 80, 70, 60, 50, 40, 30, 20, 10)
            }
        }

        class CpuTeXBar(
            xe: Double = 0.0,
            text: String = "",
            n: Int = 0,
            color: String = "",
        ): TeXBar(xe, text, n, color) {
            override fun getTeXTemplate(): String = "cpu-bar.tex"

            companion object {
                fun make(l: List<ProcessInfo2>): List<CpuTeXBar> {
                    /*
                  🐢 : convert it. In particular, we need to calculate the `xe`, which should be

                    xe = X * o.cpuPercent/ max(cpuPercent)
                    // where X is pre-set such as 10cm

                    the maximum of cpuPercent is that of the first one.

                    🦜 : Oh, so `l` is supposed to be sorted by cpuPercent.

                    🐢 : Yeah.
                 */
                    val X = 10.0
                    val l2: MutableList<CpuTeXBar> = ArrayList()
                    for (i in l.indices) {
                        val o = l[i]
                        // val xe = X * o.cpuPercent / l[0].cpuPercent
                        val xe = if (l[0].cpuPercent == 0.0) 0.0 else X * o.cpuPercent / l[0].cpuPercent
                        val text =
                            "\\texttt{" + o.name + "[pid=" + o.pid + "] " + String.format("%.2f", o.cpuPercent) + "\\%}"
                        // \aSimpleBar[\mycola!40]{\x}{-\y}{5cm}{\texttt{chrome[pid=1234] 23\%}}
                        val col = "\\mycola!" + colorGradients[i % colorGradients.size]
                        l2.add(CpuTeXBar(xe, text, i + 1, col))
                    }

                    return l2
                }
            }
        }

        class MemoryTeXBar(
            xe: Double = 0.0,
            text: String = "",
            n: Int = 0,
            color: String = "",
        ): TeXBar(xe, text, n, color) {
            override fun getTeXTemplate(): String = "memory-bar.tex"

            companion object {
                fun make(l: List<ProcessInfo2>): List<MemoryTeXBar> {
                    /*
                     🐢 : Similar to `CpuTeXBar`, but instead we use `memPercent` here.
                 */
                    val X = 10.0
                    val l2: MutableList<MemoryTeXBar> = ArrayList()
                    for (i in l.indices) {
                        val o = l[i]
                        // val xe = X * o.memPercent / l[0].memPercent
                        val xe = if (l[0].memPercent == 0.0f) 0.0 else X * o.memPercent / l[0].memPercent
                        val text =
                            "\\texttt{" + o.name + "[pid=" + o.pid + "] " + String.format("%.2f", o.memPercent) + "\\%}"
                        val col = "\\mycola!" + colorGradients[i % colorGradients.size]
                        l2.add(MemoryTeXBar(xe, text, i + 1, col))
                    }
                    return l2
                }
            }
        }

        class ReadTeXBar(
            xe: Double = 0.0,
            text: String = "",
            n: Int = 0,
            color: String = "",
        ): TeXBar(xe, text, n, color) {

            // override val teXTemplate: String
            //     get() = "disk-read-bar.tex"
            override fun getTeXTemplate(): String = "disk-read-bar.tex"

            companion object {
                fun make(l: List<ProcessInfo2>): List<ReadTeXBar> {
                    /*
                     🐢 : Similar to `CpuTeXBar`, but instead we use `readBytes` here.
                */
                    val X = 10.0
                    val l2: MutableList<ReadTeXBar> = ArrayList()
                    for (i in l.indices) {
                        val o = l[i]
                        // val xe = X * o.readBytes!! / l[0].readBytes!!
                        val xe = if (l[0].readBytes == 0L) 0.0 else X * o.readBytes / l[0].readBytes
                        val text = "\\texttt{" + o.name + "[pid=" + o.pid + "] " +
                                TeXKvEntry.humanizeBytes(o.readBytes) + "}"
                        val col = "\\mycola!" + colorGradients[i % colorGradients.size]
                        l2.add(ReadTeXBar(xe, text, i + 1, col))
                    }
                    return l2
                }
            }
        }

        class WriteTeXBar(
            xe: Double = 0.0,
            text: String = "",
            n: Int = 0,
            color: String = "",
        ): TeXBar(xe, text, n, color) {

            override fun getTeXTemplate(): String = "disk-write-bar.tex"

            companion object {
                fun make(l: List<ProcessInfo2>): List<WriteTeXBar> {
                    /*
                     🐢 : Similar to `CpuTeXBar`, but instead we use `writeBytes` here.
                     */
                    val X = 10.0
                    val l2: MutableList<WriteTeXBar> = ArrayList()
                    for (i in l.indices) {
                        val o = l[i]
                        // val xe = X * o.writeBytes!! / l[0].writeBytes!!
                        val xe = if (l[0].writeBytes == 0L) 0.0 else X * o.writeBytes / l[0].writeBytes
                        val text = "\\texttt{" + o.name + "[pid=" + o.pid + "] " +
                                TeXKvEntry.humanizeBytes(o.writeBytes) + "}"
                        val col = "\\mycola!" + colorGradients[i % colorGradients.size]
                        l2.add(WriteTeXBar(xe, text, i + 1, col))
                    }
                    return l2
                }
            }
        }                       //class WriteTeXBar


        companion object {
            fun convertFromPb(i: ProcessInfo): ProcessInfo2 {
                val o = ProcessInfo2()
                o.name = i.name
                o.user = i.user
                o.pid = i.pid
                o.ppid = i.ppid
                o.cwd = i.cwd
                o.cpuPercent = i.cpuPercent
                o.memPercent = i.memPercent
                o.readCount = i.ioCounters.readCount
                o.writeCount = i.ioCounters.writeCount
                o.readBytes = i.ioCounters.readBytes
                o.writeBytes = i.ioCounters.writeBytes
                o.createTime = i.createTime
                o.isRunning = i.isRunning
                o.isForeground = i.isForeground

                // // o.setCmd(i.getCmdList());
                // /*
                //   🦜 : There's something weird about persisting the List, so Let's
                //   just convert from google's List to java's List...
                //  */

                for (s in i.cmdlineList) {
                    // cap the length to 254
                    // if (s.length > 254) {
                    //     s = s.substring(0, 254)
                    // }
                    // o.cmdline.add(s)
                    o.cmdline.add(s.take(254)) // 🦜: Use the considerate
                }

                /*
              cap the Cmd to 5, also cap each entry to 100

              🦜 : Shouldn't be a big issure, right?

              🐢 : It seems like if these numbers are *too big* or *too small*, hibernate will
              fail to save it to db......Kinda mysterious...

              🦜 : Nope, it turns out that failing to save is because pb's List
              is mutable which is not acceptable by Hibernate. The solution is
              to make sure that the List is an immutable one.....
             */
                o.exePath = i.exePath
                o.hash = i.hash
                return o
            }
        }

    }                           //class ProcessInfo2


    class DiskInfo2(
        var path: String = "",
        var fstype: String = "",
        var total: Long = 0,
        var free: Long = 0,
        var used: Long = 0,
        var usedPercent: Double = 0.0,
        var inodesTotal: Long = 0,
        var inodesUsed: Long = 0,
        var inodesFree: Long = 0,
        var inodesUsedPercent: Double = 0.0,
    )

    class MemoryInfo2(
        var total: Long = 0,
        var available: Long = 0,
        var used: Long = 0,
        var usedPercent: Double = 0.0,
    )

    class CpuInfo2(
        var cpu: Int = 0,
        var vendorId: String = "",
        var family: String = "",
        var model: String = "",
        var modelName: String = "",
        var stepping: Int = 0,
        var mhz: Double = 0.0,
        var cacheSize: Int = 0,
        var physicalId: String = "",
        var coreId: String = "",
        var cores: Int = 0,
    )

    class IOCountersInfo2(
        var readCount: Long = 0,
        var mergedReadCount: Long = 0,
        var writeCount: Long = 0,
        var mergedWriteCount: Long = 0,
        var readBytes: Long = 0,
        var writeBytes: Long = 0,
        var readTime: Long = 0,
        var writeTime: Long = 0,
        var iopsInProgress: Long = 0,
        var ioTime: Long = 0,
        var weightedIO: Long = 0,
        var name: String = "",
        var serialNumber: String = "",
        var label: String = "",
    )

    class GetResourceInfoRpl2(
        var cpu_percent: Double = 0.0,
        var cpu: MutableList<CpuInfo2> = ArrayList(),
        var memory_info: MemoryInfo2 = MemoryInfo2(),
        var disk: DiskInfo2 = DiskInfo2(),
        @get:JsonProperty("iOCountersInfo")
        var iOCountersInfo: MutableMap<String, IOCountersInfo2> = HashMap(),
        var hostname: String = "",
    ){
        companion object {
            fun convertFromPb(r: GetResourceInfoRpl): GetResourceInfoRpl2 {
                val r2 = GetResourceInfoRpl2()
                r2.cpu_percent = r.cpuPercent
                r2.hostname = r.hostname

                // 2.1 get the cpuInfos
                for (i in r.cpuInfosList) {
                    val i2 = CpuInfo2()
                    i2.cpu = i.cpu
                    i2.vendorId = i.vendorId
                    i2.family = i.family
                    i2.model = i.model
                    i2.modelName = i.modelName
                    i2.stepping = i.stepping
                    i2.mhz = i.mhz
                    i2.cacheSize = i.cacheSize
                    i2.physicalId = i.physicalId
                    i2.coreId = i.coreId
                    i2.cores = i.cores
                    r2.cpu.add(i2)
                }
                // 2.2 get the memoryInfo
                r2.memory_info.let {
                    it.total = r.memoryInfo.total
                    it.available = r.memoryInfo.available
                    it.used = r.memoryInfo.used
                    it.usedPercent = r.memoryInfo.usedPercent
                }

                // 2.3 get the diskInfo
                r2.disk.let {
                    it.path = r.diskInfo.path
                    it.fstype = r.diskInfo.fstype
                    it.total = r.diskInfo.total
                    it.free = r.diskInfo.free
                    it.used = r.diskInfo.used
                    it.usedPercent = r.diskInfo.usedPercent
                    it.inodesTotal = r.diskInfo.inodesTotal
                    it.inodesUsed = r.diskInfo.inodesUsed
                    it.inodesFree = r.diskInfo.inodesFree
                    it.inodesUsedPercent = r.diskInfo.inodesUsedPercent
                }

                // 2.4 get the ioCountersInfo
                for ((k,v) in r.ioCountersInfoMap) {
                    val i2 = IOCountersInfo2()
                    i2.readCount = v.readCount
                    i2.mergedReadCount = v.mergedReadCount
                    i2.writeCount = v.writeCount
                    i2.mergedWriteCount = v.mergedWriteCount
                    i2.readBytes = v.readBytes
                    i2.writeBytes = v.writeBytes
                    i2.readTime = v.readTime
                    i2.writeTime = v.writeTime
                    i2.iopsInProgress = v.iopsInProgress
                    i2.ioTime = v.ioTime
                    i2.weightedIO = v.weightedIO
                    i2.name = v.name
                    i2.serialNumber = v.serialNumber
                    i2.label = v.label
                    r2.iOCountersInfo[k] = i2
                }

                return r2
            }
        }
    }                               //class GetResourceInfoRpl2

    class AddrPort(
        // @JsonIgnore @Hidden @Id @GeneratedValue var id: Long = 0,
        var addr: String = "",
        var port: Int = 0,
    )


    // [2024-02-29] 🦜 : Update, no need to peresist this in db
    class ConnectionStat(
        // @Hidden @Id @JsonIgnore @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0,
        var fd: Int = 0,
        var family: Int = 0,
        var type: Int = 0,
        //  @OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true) @JoinColumn(name = "local_addr_id") var localAddr: AddrPort? = null,
        // @OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true) @JoinColumn(name = "remote_addr_id") var remoteAddr: AddrPort? = null,
        var localAddr: AddrPort? = null,
        var remoteAddr: AddrPort? = null,
        var status: String = "",
        // @ElementCollection(fetch = FetchType.EAGER) var uids: MutableList<Int> = ArrayList(),
        var uids: List<Int> = ArrayList(),
        var pid: Int = 0,
        var closed: Boolean = false,
        var exeName: String = "",
    ) : ITeXWorkable {

        val typeString: String
            get() {
                var s = if (type == 1) "tcp" else "udp"
                s += if (family == 2) "v4" else "v6"
                return s
            }

        override fun getTeXEmpty(): String = "-- & -- & -- & -- & -- \\\\"
        override fun getTeXTemplate(): String = "port-listening.tex"

        override fun toTeXLine(): String {
            var s = ""
            // s += localAddr!!.addr + ":" + localAddr!!.port + " & "
            s += localAddr?.let { "${it.addr}:${it.port} &" } ?: "-- &"
            // s += remoteAddr!!.addr + ":" + remoteAddr!!.port + " & "
            s += remoteAddr?.let { "${it.addr}:${it.port} &" } ?: "-- &"
            s += "\\texttt{" + status + "} & "
            s += exeName + " & "
            s += typeString + " \\\\"
            return s.replace("_".toRegex(), "\\\\_")
        }

    }
    

    class ManyCounters(
        var x1: Long = 0,
        var x2: Long = 0,
        var x3: Long = 0,
        var y1: Long = 0,
        var y2: Long = 0,
        var y3: Long = 0,
        var y4: Long = 0,
        var y5: Long = 0,
        var z1: Long = 0,
        var z2: Long = 0,
        var a1: Long = 0,
        var a2: Long = 0,
        var a3: Long = 0,
        var b1: Long = 0,
        var dailyRecords: MutableList<DailyRecord> = ArrayList()
    )

    class DailyRecord(
        var date: Date? = null,
        var x1: Long = 0,
        var y1: Long = 0,
        var y2: Long = 0,
        var z1: Long = 0,
        var z2: Long = 0,
        var z3: Long = 0) {

        val dateDay: String
            get() {
                // return YYYY-MM-DD
                val d = old2New(date)
                return d.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
            }
    }

    @MappedSuperclass
    open class ApplicantIn(
        open var name: String = "name",
        open var phone: String = "",
        open var sfz: String = "",
        open var code: String = "",
        open var department: String = "",
        open var expiryTime: Date? = null,
    )

    // @Entity
    // class Applicant : ApplicantIn() {
    //     @Id
    //     @GeneratedValue(strategy = GenerationType.IDENTITY)
    //     var id: Long? = null
    //     var createTime: Date? = null

    //     var illegal: Boolean = true
    //     val expired: Boolean
    //         get() = Date().after(expiryTime)
    // }

    @Entity
    class Applicant(
        @Id @GeneratedValue var id: Long = 0,
        var createTime: Date? = null,
        var illegal: Boolean = true,
    ) : ApplicantIn() {
        val expired: Boolean
            get() = Date().after(expiryTime)

        companion object {
            fun mockList(n: Int): List<Applicant> {
                val users = users
                val phones = arrayOf("188-1239-2345", "188-5784-2379", "188-2346-5667")
                val names = arrayOf("李翔宇", "张丽安")
                val sfzs = arrayOf("123456789012345678", "123456789012345679", "123456789012345670")

                val l: MutableList<Applicant> = ArrayList()
                for (i in 0 until n) {
                    val a = Applicant()

                    a.phone = phones[i % phones.size]
                    a.name = names[i % names.size]
                    a.sfz = sfzs[i % sfzs.size]

                    try {
                        // a.setCode(Utils.generateRandomSha256Hash())
                        a.code = Utils.generateRandomSha256Hash()
                    } catch (e: NoSuchAlgorithmException) {
                        a.code = "xxxxaaaa11112222xxxxbbbb"
                    }
                    a.department = "部门$i"

                    if (i < 5) {
                        // a.setExpiryTime(Date())
                        a.expiryTime = Date()
                    } else {              // not expired
                        val c = Calendar.getInstance()
                        // c[2040, 1, 1, 1, 1] = 1  // 🦜 : This is a kotlin magic abuse..
                        c.set(2040, 1, 1, 1, 1, 1)
                        a.expiryTime = c.time
                    }

                    a.createTime = Date()
                    l.add(a)
                }
                return l
            }
        }
    }

    @StaticMetamodel(ApplicantIn::class)
    internal abstract class ApplicantIn_ {
        companion object {
            @Volatile
            var code: SingularAttribute<Applicant, String>? = null

            @Volatile
            var sfz: SingularAttribute<Applicant, String>? = null

            @Volatile
            var department: SingularAttribute<Applicant, String>? = null

            @Volatile
            var name: SingularAttribute<Applicant?, String>? = null

            @Volatile
            var phone: SingularAttribute<Applicant?, String>? = null

            @Volatile
            var expiryTime: SingularAttribute<Applicant?, Date?>? = null

            fun nameContains(name: String): Specification<Applicant?> {
                return Specification { root: Root<Applicant?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(Companion.name), "%$name%")
                }
            }

            fun phoneContains(phone: String): Specification<Applicant?> {
                return Specification { root: Root<Applicant?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(Companion.phone), "%$phone%")
                }
            }

            fun expiryTimeAfter(expiryTime: Date): Specification<Applicant?> {
                return Specification { root: Root<Applicant?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.greaterThan(root.get(Companion.expiryTime), expiryTime)
                }
            }

            fun expiryTimeBefore(expiryTime: Date): Specification<Applicant?> {
                return Specification { root: Root<Applicant?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.lessThan(root.get(Companion.expiryTime), expiryTime)
                }
            }
        }
    }                           //ApplicantIn_

    @StaticMetamodel(Applicant::class)
    internal object Applicant_ : ApplicantIn_() {
        @Volatile
        var createTime: SingularAttribute<Applicant?, Date?>? = null

        @Volatile
        var id: SingularAttribute<Applicant, Long>? = null

        @Volatile
        var illegal: SingularAttribute<Applicant, Boolean>? = null

        fun createTimeAfter(createTime: Date): Specification<Applicant?> {
            return Specification { root: Root<Applicant?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.greaterThan(root.get(Applicant_.createTime), createTime)
            }
        }

        fun createTimeBefore(createTime: Date): Specification<Applicant?> {
            return Specification { root: Root<Applicant?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.lessThan(root.get(Applicant_.createTime), createTime)
            }
        }

    }

    /*

     class Error {
     private var code = 0
     private var msg: String? = null

     constructor(code: Int, msg: String?) {
     this.code = code
     this.msg = msg
     }

     constructor()

     }
     */

    class Error(var code: Int = 0, var msg: String = "Success"){
        companion object {
            var SUCCESS: Error = Error(0, "Success")
            var DUPLICATE_ENTRY: Error = Error(1001, "duplicate entry")
            var PDF_GENERATE_FAILED: Error = Error(1002, "pdf generate failed")
        }
    }


    /*
     @Data
     @MappedSuperclass
     internal open class ChangePswdReq {
     @Hidden
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     var id: Long? = null
     var pswd: String? = null
     var expiryTime: Date? = null
     }

     @Entity
     @Data
     @EqualsAndHashCode(callSuper = false) // if base class is @Data, ignore warning from lombok
     internal class ChangePswdRecord : ChangePswdReq() {
     // @NotNull
     var createTime: Date? = null
     }
     */

    @MappedSuperclass
    open class ChangePswdReq(
        @Id @GeneratedValue var id: Long = 0,
        var pswd: String = "",
        var expiryTime: Date? = null,
    )

    @Entity
    class ChangePswdRecord(var createTime: Date? = null) : ChangePswdReq()


    // @Data
    // @MappedSuperclass
    // open class ProcessRegisterBase {
    //     var userName: String? = null // empty means "any"
    //     @JvmField
    //     var exeName: String? = null
    //     var hash: String? = null // empty means skipping hash check
    //     var type: String? = null // can be "ok" | "must"
    //     var reason: String? = null
    // }

    @MappedSuperclass
    open class ProcessRegisterBase(
        var userName: String = "",
        var exeName: String = "",
        var hash: String = "",
        var type: String = "",
        var reason: String = "",
    )

    @StaticMetamodel(ProcessRegisterBase::class)
    abstract class ProcessRegisterBase_ {
        companion object {
            @Volatile
            var userName: SingularAttribute<ProcessRegisterBase?, String>? = null

            @Volatile
            var exeName: SingularAttribute<ProcessRegisterBase?, String>? = null

            @Volatile
            var hash: SingularAttribute<ProcessRegisterBase?, String>? = null

            @Volatile
            var type: SingularAttribute<ProcessRegisterBase?, String>? = null

            // --------------------------------------------------
            // 🦜 : for ProcessRegister
            fun userNameEquals(userName: String): Specification<ProcessRegister?> {
                return Specification { root: Root<ProcessRegister?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.equal(root.get(Companion.userName), userName)}
            }

            fun userNameContains(userName: String): Specification<ProcessRegister?> {
                return Specification { root: Root<ProcessRegister?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(Companion.userName), "%$userName%")
                }
            }

            fun exeNameEquals(exeName: String): Specification<ProcessRegister?> {
                return Specification { root: Root<ProcessRegister?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.equal(root.get(Companion.exeName), exeName)
                }
            }

            fun exeNameContains(exeName: String): Specification<ProcessRegister?> {
                return Specification { root: Root<ProcessRegister?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(Companion.exeName), "%$exeName%")
                }
            }

            fun hashEquals(hash: String): Specification<ProcessRegister?> {
                return Specification { root: Root<ProcessRegister?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.equal(root.get(Companion.hash), hash)
                }
            }

            fun typeEquals(type: String): Specification<ProcessRegister?> {
                return Specification { root: Root<ProcessRegister?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.equal(root.get(Companion.type), type)
                }
            }

            // --------------------------------------------------
            // 🦜 : for ProcessRegisterFailure
            fun userNameContains_f(userName: String): Specification<ProcessRegisterFailure?> {
                return Specification { root: Root<ProcessRegisterFailure?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(Companion.userName), "%$userName%")
                }
            }

            fun exeNameEquals_f(exeName: String): Specification<ProcessRegisterFailure?> {
                return Specification { root: Root<ProcessRegisterFailure?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.equal(root.get(Companion.exeName), exeName)
                }
            }

            fun exeNameContains_f(exeName: String): Specification<ProcessRegisterFailure?> {
                return Specification { root: Root<ProcessRegisterFailure?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(Companion.exeName), "%$exeName%")
                }
            }

            fun hashEquals_f(hash: String): Specification<ProcessRegisterFailure?> {
                return Specification { root: Root<ProcessRegisterFailure?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.equal(root.get(Companion.hash), hash)
                }
            }

            fun typeEquals_f(type: String): Specification<ProcessRegisterFailure?> {
                return Specification { root: Root<ProcessRegisterFailure?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.equal(root.get(Companion.type), type)
                }
            }
        }
    }

    class TeXKvEntry(var key: String, var value: String) : ITeXWorkable {

        override fun getTeXEmpty(): String =  ""

        override fun getTeXTemplate(): String  = "kv.tex"

        override fun toTeXLine(): String  = "\\MySet{$key}{$value}"

        companion object {
            fun humanizeBytes(bytes: Long): String {
                return if (bytes < 1024) {
                    bytes.toString() + "B"
                } else if (bytes < 1024 * 1024) {
                    String.format("%.2f", bytes / 1024.0) + "KB"
                } else if (bytes < 1024 * 1024 * 1024) {
                    String.format("%.2f", bytes / 1024.0 / 1024.0) + "MB"
                } else {
                    String.format("%.2f", bytes / 1024.0 / 1024.0 / 1024.0) + "GB"
                }
            }
        }
    }

    @Entity
    class ProcessRegister(
        @Id @GeneratedValue var id : Long = 0,
        var createTime: Date? = null,
    ) : ProcessRegisterBase(), ITeXWorkable {
        /*
          register /rĕj′ĭ-stər/
          noun
          1. A formal or official recording of items, names, or actions.
          2. A book for such entries.
          3. An entry in such a record. <- 🦜 : here we mean this
         */
        @JsonIgnore
        override fun getTeXEmpty(): String {
            return "-- & -- & -- & -- & -- \\\\"
        }

        @JsonIgnore
        override fun getTeXTemplate(): String {
            return "process-register.tex"
        }

        override fun toTeXLine(): String {
            /*
              🦜 : For each, write the line
              <user> & <name> & <hash> & <type> & <time> \\
            */

            var s = ""
            s += texttt(userName) + " & "
            s += texttt(exeName) + " & "
            s += texttt(hash) + " & "
            s += (if (type === "ok") "注册允许应用" else "关键应用") + " & "
            // YYYY - MM - DD HH: MM: SS
            s += old2New(createTime).format(dtf) + " \\\\"

            // replace all the `_` with `\_`
            return s.replace("_".toRegex(), "\\\\_")
        }

        companion object {
            /*
          🦜 : ⚠️ When you define a ctor, the default ctor is gone....
         */
            @JvmStatic
            fun MakeOkRegister(exeName: String, reason: String): ProcessRegister {
                val o = ProcessRegister()
                // o.userName = "";
                o.exeName = exeName
                // o.hash = "";
                o.type = "ok"
                o.reason = reason
                o.createTime = Date()
                return o
            }

            fun texttt(s: String?): String {
                if (s == null || s.length == 0) {
                    return "--"
                }
                return "\\texttt{$s}"
            }
        }
    }

    @StaticMetamodel(ProcessRegister::class)
    object ProcessRegister_ : ProcessRegisterBase_() {
        @Volatile
        var id: SingularAttribute<ProcessRegister, Long>? = null
    }

    @Entity
    class ProcessRegisterFailure(
        @Id @GeneratedValue var id: Long = 0,
        var timeOfDiscovery: Date? = null,
        var fullCommandLine: String = "",
    ) : ProcessRegisterBase(), ITeXWorkable {

        @JsonIgnore
        override fun getTeXEmpty(): String {
            return "-- & -- & -- & -- & -- \\\\"
        }

        @JsonIgnore
        override fun getTeXTemplate(): String {
            return "process-failure.tex"
        }

        override fun toTeXLine(): String {
            /*
              🦜 : For each, write the line
              <user> & <name> & <hash> & <reason> & <time> \\
            */

            val name0 = fullCommandLine.take(20) + if (fullCommandLine.length > 20) "..." else ""
            var s = ""
            s += "$userName & "
            s += "$name0 & "
            s += "$hash & "
            s += "$reason &"
            // YYYY - MM - DD HH: MM: SS
            s += old2New(timeOfDiscovery).format(dtf) + " \\\\"

            // replace all the `_` with `\_`
            return s.replace("_".toRegex(), "\\\\_")
        }
    }

    @StaticMetamodel(ProcessRegisterFailure::class)
    object ProcessRegisterFailure_ : ProcessRegisterBase_() {
        @Volatile
        var id: SingularAttribute<ProcessRegisterFailure, Long>? = null

        @Volatile
        var timeOfDiscovery: SingularAttribute<ProcessRegisterFailure, Date>? = null
    }

    @Entity
    class GenericCounter(
        @Id @GeneratedValue var id: Long = 0,
        var name: String = "",
        var createTime: Date? = null,
    ) {
        /*
         🦜 : This counter is initially used to count the check time.
         */

        companion object {
            fun create(n: String): GenericCounter {
                val o = GenericCounter()
                o.name = n
                // o.setCreateTime(Date())
                o.createTime = Date()
                return o
            }
        }
    }                           //class GenericCounter


    @Entity
    open class SafeboxRecord(
        open @Id var user: String = "",
        open var passwd: String = "",
    )

    class SafeboxWithMore(
        var lastAccessTime: Date? = null,
        var numFiles: Long = 0,
    ) : SafeboxRecord()

    // @Data
    // @Entity
    // class SafeboxFile {
    //     @Id
    //     @GeneratedValue(strategy = GenerationType.IDENTITY)
    //     var id: Long? = null
    //     var user: String? = null
    //     var fileName: String? = null
    //     var size: Long? = null
    //     var lastModifyTime: Date? = null
    //     var lastAccessTime: Date? = null
    //     var permissions: String? = null
    // }

    @Entity
    class SafeboxFile(
        @Id @GeneratedValue var id: Long = 0,
        var user: String = "",
        var fileName: String = "",
        var size: Long = 0,
        var lastModifyTime: Date? = null,
        var lastAccessTime: Date? = null,
        var permissions: String = "",
    )

    @Entity
    class BashHistoryEntry2(
        var cmd: String = "",
        var time: String = "", // 🦜 : can be special value: unknown
        var user: String = "",
        @Id @GeneratedValue var id: Long = 0,
    ) {
        companion object {
            // @JvmStatic
            fun convertFromPb(e: BashHistoryEntry) = BashHistoryEntry2(e.cmd, e.time, e.user)
        }
    }

    @StaticMetamodel(value = BashHistoryEntry2::class)
    internal abstract class BashHistoryEntry2_ {
        companion object {
            @Volatile
            var cmd: SingularAttribute<BashHistoryEntry2?, String>? = null

            @Volatile
            var time: SingularAttribute<BashHistoryEntry2?, String>? = null

            @Volatile
            var user: SingularAttribute<BashHistoryEntry2?, String>? = null

            fun cmdContains(cmd: String): Specification<BashHistoryEntry2?> {
                return Specification { root: Root<BashHistoryEntry2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(BashHistoryEntry2_.cmd), "%$cmd%")
                }
            }

            fun userContains(user: String): Specification<BashHistoryEntry2?> {
                return Specification { root: Root<BashHistoryEntry2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.like(root.get(BashHistoryEntry2_.user), "%$user%")
                }
            }

            fun timeAfter(time: String): Specification<BashHistoryEntry2?> {
                return Specification { root: Root<BashHistoryEntry2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.greaterThan(root.get(BashHistoryEntry2_.time), time)
                }
            }

            fun timeBefore(time: String): Specification<BashHistoryEntry2?> {
                return Specification { root: Root<BashHistoryEntry2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                    builder.lessThan(root.get(BashHistoryEntry2_.time), time)
                }
            }
        }
    }                           //class BashHistoryEntry2_

    class InterfaceStat(
        var index: Int = 0,
        var mtu: Int = 0,
        var name: String = "",
        var hardwareAddr: String = "",
        var flags: List<String>? = null,
        var addrs: List<String>? = null,
        var ioCounterStat: IOCounterStat = IOCounterStat(),
        var defaultGateway: String = ""
    )

    class IOCounterStat(
        var bytesSent: Long = 0,
        var bytesRecv: Long = 0,
        var packetsSent: Long = 0,
        var packetsRecv: Long = 0,
        var errin: Long = 0,
        var errout: Long = 0,
        var dropin: Long = 0,
        var dropout: Long = 0,
        var recvSpeedBytePerSec: Long = 0,
        var sentSpeedBytePerSec: Long = 0,
    )


    // class InboundRule2 : ITeXWorkable {
    //     var type: String? = null // tcp | udp | icmp | all
    //     var portRange: String? = null
    //     var source: String? = null
    //     var ipv6: Boolean? = null

    //     override fun toTeXLine(): String {
    //         /*
    //           🦜 : For each, write the line
    //           <type> & <portRange> & <source> & <ipv6> \\
    //         */

    //         var s = ""
    //         s += "$type & "
    //         s += "$portRange & "
    //         s += "$source & "
    //         s += (if (ipv6!!) "\\emoji{keycap-6}" else "\\emoji{keycap-4}") + " \\\\"

    //         // replace all the `_` with `\_`
    //         return s.replace("_".toRegex(), "\\\\_")
    //     }

    //     fun convertToPb(): InboundRule {
    //         val b = InboundRule.newBuilder()
    //         if (type == "tcp") {
    //             b.setType(InboundRuleType.tcp)
    //         } else if (type == "udp") {
    //             b.setType(InboundRuleType.udp)
    //         } else if (type == "icmp") {
    //             b.setType(InboundRuleType.icmp)
    //         } else {
    //             b.setType(InboundRuleType.all)
    //         }

    //         b.setPortRange(portRange)
    //         b.setSource(source)
    //         b.setIpv6(ipv6!!)

    //         return b.build()
    //     }

    //     companion object {
    //         @get:JsonIgnore
    //         val teXTemplate: String = "inbound-rules.tex"
    //             get() = Companion.field

    //         @get:JsonIgnore
    //         val teXEmpty: String = "-- & -- & -- & --\\\\"
    //             get() = Companion.field
    //     }
    // } // class InboundRule2

    class InboundRule2(
        var type: String = "", // tcp | udp | icmp | all
        var portRange: String = "",
        var source: String = "",
        var ipv6: Boolean = false,
    ) : ITeXWorkable {
        override fun toTeXLine(): String {
            /*
              🦜 : For each, write the line
              <type> & <portRange> & <source> & <ipv6> \\
            */

            var s = ""
            s += "$type & "
            s += "$portRange & "
            s += "$source & "
            s += (if (ipv6) "\\emoji{keycap-6}" else "\\emoji{keycap-4}") + " \\\\"

            // replace all the `_` with `\_`
            return s.replace("_".toRegex(), "\\\\_")
        }

        override fun getTeXEmpty(): String {
            return "-- & -- & -- & --\\\\"
        }

        override fun getTeXTemplate(): String {
            return "inbound-rules.tex"
        }

        fun convertToPb(): InboundRule {
            val b = InboundRule.newBuilder()

            if (type == "tcp") {
                b.type = InboundRuleType.tcp
            } else if (type == "udp") {
                b.type = InboundRuleType.udp
            } else if (type == "icmp") {
                b.type = InboundRuleType.icmp
            } else {
                b.type = InboundRuleType.all
            }

            b.portRange = portRange
            b.source = source
            b.ipv6 = ipv6

            return b.build()
        }

    } // class InboundRule2


    // @Entity
    // @Data
    // class LoginInfo2 {
    //     @Id
    //     @GeneratedValue(strategy = GenerationType.IDENTITY)
    //     var id: Long? = null

    //     var user: String? = null
    //     var terminal: String? = null
    //     var loginIp: String? = null
    //     var loginTime: Date? = null
    //     var logoutTime: String? = null // 登出时间 【特殊值释义 crash：系统崩溃或意外关机；down:用户图形界面登出】

    //     companion object {
    //         /*
    //       🦜 : Why `loginTime` is stored as Date, but not `logoutTime`?

    //       🐢 : Because `logoutTime` is not always a valid date, it can be
    //       special values such as `crash` or `down`.
    //     */
    //         @JvmStatic
    //         fun convertFromPb(i: LoginInfo): LoginInfo2 {
    //             val i2 = LoginInfo2()
    //             i2.setUser(i.user)
    //             i2.setTerminal(i.terminal)
    //             i2.setLoginIp(i.loginIp)
    //             i2.setLoginTime(
    //                 new2Old(LocalDateTime.parse(i.loginTime, dtf)) // -> the old java.util.Date
    //             )
    //             i2.setLogoutTime(i.logoutTime) // this can be "crash" or "down"
    //             return i2
    //         }
    //     }
    // }

    @Entity
    class LoginInfo2(
        @Id @GeneratedValue var id: Long = 0,
        var user: String = "",
        var terminal: String = "",
        var loginIp: String = "",
        var loginTime: Date? = null,
        var logoutTime: String = "", // 登出时间 【特殊值释义 crash：系统崩溃或意外关机；down:用户图形界面登出】
    ) {
        companion object {
            /*
             🦜 : Why `loginTime` is stored as Date, but not `logoutTime`?

             🐢 : Because `logoutTime` is not always a valid date, it can be
             special values such as `crash` or `down`.
             */
            fun convertFromPb(i: LoginInfo): LoginInfo2 {
                val i2 = LoginInfo2()
                i2.user = i.user
                i2.terminal = i.terminal
                i2.loginIp = i.loginIp
                i2.loginTime = new2Old(LocalDateTime.parse(i.loginTime, dtf)) // -> the old java.util.Date
                i2.logoutTime = i.logoutTime // this can be "crash" or "down"
                return i2
            }
        }
    }                           //class LoginInfo2

    @StaticMetamodel(LoginInfo2::class)
    internal object LoginInfo2_ {
        @Volatile
        var user: SingularAttribute<LoginInfo2?, String>? = null

        @Volatile
        var loginTime: SingularAttribute<LoginInfo2?, Date>? = null

        fun userContains(user: String): Specification<LoginInfo2?> {
            return Specification { root: Root<LoginInfo2?>, _: CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.like(root.get(LoginInfo2_.user), "%$user%")
            }
        }

        fun loginTimeAfter(loginTime: Date): Specification<LoginInfo2?> {
            return Specification { root: Root<LoginInfo2?>, _ : CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.greaterThan(root.get(LoginInfo2_.loginTime), loginTime)
           }
        }

        fun loginTimeBefore(loginTime: Date): Specification<LoginInfo2?> {
            return Specification { root: Root<LoginInfo2?>, _ : CriteriaQuery<*>?, builder: CriteriaBuilder ->
                builder.lessThan(root.get(LoginInfo2_.loginTime), loginTime)
            }
        }
    }                           //class LoginInfo2_

    class UserInfo2(
        var uid: String = "",
        var name: String = "",
        var homeDir: String = "",
        var description: String = "",
        var shell: String = "",
    ){

        val uidInt : Int
            get() {
                try {
                    return uid.toInt()
                } catch (e: NumberFormatException) {
                    return -1
                }
            }

        companion object {
            fun convertFromPb(i: UserInfo): UserInfo2 {
                val i2 = UserInfo2()
                i2.uid = i.uid
                i2.name = i.name
                i2.homeDir = i.homeDir
                i2.description = i.description
                i2.shell = i.shell
                return i2
            }
        }
    }                           //class UserInfo2


}                               //object Dt
